package org.siam.impl.common {

	import flash.utils.describeType;
	import flash.utils.getQualifiedClassName;
	
	import mx.core.ClassFactory;
	
	import org.siam.utils.ClassUtils;
	import org.siam.utils.RuntimeError;
	
	public class RegistryFactory extends XMLFactory {
		
		private static const SUPPORTED_TYPES:Object = {
			String: true, Object: true , Boolean: true, uint: true, int: true, Number: true 
		};
		
		public static const UICOMPONENT_CLASS:Class = ClassUtils.resolveName("mx.core.UIComponent");
		
		private var _validType:Class;
		private var _objectName:String;
		private var _hasObjectFunction:Function;
		private var _registerObjectFunction:Function;
		
		public function RegistryFactory(input:XML, 
										validType:Class, 
										objectName:String,
										hasObjectFunction:Function,
										registerObjectFunction:Function) {
			super(input);
			
			_validType = validType;
			_objectName = objectName;
			_hasObjectFunction = hasObjectFunction;
			_registerObjectFunction = registerObjectFunction;
		}
		
		public function get validType():Class {
			return _validType;
		}
		
		public function get objectName():String {
			return _objectName;
		}
		
		public function get hasObjectFunction():Function {
			return _hasObjectFunction;
		}
		
		public function get registerObjectFunction():Function {
			return _registerObjectFunction;
		}
		
		public function parseInput():void {
			if (input) {
				for each (var classXML:XML in input["class"]) {
					// Validates class name
					var objectClassName:String = classXML["@name"];
					if (!objectClassName) {
						throw new RuntimeError("A %s class must have a valid name", 
												objectName);
					}
					// Try to resolve formatter class
					var objectClass:Class = null;
					try {
						objectClass = ClassUtils.resolveName(objectClassName);
						if (!ClassUtils.isSubclass(objectClass, validType)) {
							throw new RuntimeError("The %s class '%s' is not a valid type. " +
												   "It must derive from '%s'", 
												   objectName, objectClassName, getQualifiedClassName(validType));
						}
					} catch (e:ReferenceError) {
						throw new RuntimeError("The %s class '%s' could not be resolved. " + 
											   "Check that you typed the class name correcly. " + 
											   "If so, make sure to import it first in your application", 
											   objectName, objectClassName);
					} 	
					var classDescXML:XML = describeType(objectClass);
					var accessorsXML:Object = new Object();
					for each (var accessorXML:XML in classDescXML.factory.accessor) {
						accessorsXML[accessorXML["@name"]] = accessorXML;
					}
					for each (var objectXML:XML in classXML[objectName]) {
						// validates object id
						var objectId:String = objectXML["@id"];
						if (!objectId) {
							throw new RuntimeError("A %s must have a valid id (class '%s')", 
													objectName, objectClassName);
						} else if (hasObjectFunction(objectId)) {
							throw new RuntimeError("Duplicated %s id '%s' (class '%s')", 
													objectName, objectId, objectClassName);
						}
						// creates object using metadata
						var objectProperties:Object = new Object();
						var objectStyles:Object = new Object();
						var objectInstance:Object = new objectClass(); 
						var hasStyles:Boolean = objectInstance is UICOMPONENT_CLASS;
						if (hasStyles) {
							objectInstance.regenerateStyleCache(false);
						}
						for each (var attributeXML:XML in objectXML.attributes()) {
							var key:String = attributeXML.name();
							var value:String = attributeXML.toString();
							switch (key) {
								case "id": break; // Already set
								default:
									accessorXML = accessorsXML.hasOwnProperty(key) ? accessorsXML[key] : null;
									if (!accessorXML) {
										if (hasStyles) {
											var defaultStyleValue:* = objectInstance.getStyle(key);
											if (defaultStyleValue == undefined) {
												throw new RuntimeError("The accessor or style '%s' for %s class '%s' could not be found", 
																key, objectName, objectClassName);
											} else {
												var styleClass:Class = defaultStyleValue.constructor;
												objectStyles[key] = convertFromString(value, styleClass);
											}
										} else {
											throw new RuntimeError("The accessor '%s' for %s class '%s' could not be found", 
											 					key, objectName, objectClassName);
										}
									} else if (accessorXML["@access"] == "readonly") {
										throw new RuntimeError("The accessor '%s' for %s class '%s' is read-only!", 
																key, objectName, objectClassName);
									} else {
										var accessorTypeName:String = accessorXML["@type"];
										if (!SUPPORTED_TYPES.hasOwnProperty(accessorTypeName)) {										
											throw new RuntimeError("The accessor '%s' type '%s' for %s class '%s' is not valid!"
																	+ "Only Object and primitive String, Boolean, int, uint, Number are so far supported...", 
																	key, accessorTypeName, objectName, objectClassName);
										}
										var accessorClass:Class = ClassUtils.resolveName(accessorTypeName);
										objectProperties[key] = convertFromString(value, accessorClass);
									}
									break;
							}
						}
						var factory:ClassFactory = hasStyles ? new UIComponentFactory(objectClass, objectStyles)
															: new ClassFactory(objectClass);
						factory.properties = objectProperties;
						registerObjectFunction(objectId, factory);
					}
				}
			}
		}
		
		private function convertFromString(value:String, toType:Class):* {
			// Conversion from String to Boolean:
			// => false if the value is null or the empty string (""); true otherwis
			// http://livedocs.adobe.com/flex/3/html/help.html?content=03_Language_and_Syntax_12.html
			if (toType == Boolean && value == "false") { 
				return false;
			}
			
			return toType(value);
		}
	}
}